/*
 * Created on Dec 11, 2007
 */
package org.wicketstuff.googlecharts;

import java.awt.Color;
import java.awt.Dimension;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.WebComponent;

/**
 * @author Daniel Spiewak
 */
public class Chart extends WebComponent implements Serializable
{

	private static final long serialVersionUID = 6286305912682861488L;
	private IChartProvider provider;
	private StringBuilder url;
	private final ReadWriteLock lock = new ReentrantReadWriteLock();

	public Chart(String id, IChartProvider provider)
	{
		super(id);

		this.provider = provider;
	}

	public void invalidate()
	{
		lock.writeLock().lock();
		try
		{
			url = null;
		}
		finally
		{
			lock.writeLock().unlock();
		}
	}

	public CharSequence constructURL()
	{
		lock.writeLock().lock();
		try
		{
			if (url != null)
			{
				return url;
			}

			url = new StringBuilder("http://chart.apis.google.com/chart?");

			addParameter(url, "chs", render(provider.getSize()));
			addParameter(url, "chd", render(provider.getData()));
			addParameter(url, "cht", render(provider.getType()));
			addParameter(url, "chbh", render(provider.getBarWidth(), provider.getBarGroupSpacing()));
			addParameter(url, "chtt", render(provider.getTitle()));
			addParameter(url, "chdl", render(provider.getLegend()));
			addParameter(url, "chco", render(provider.getColors()));

			IChartFill bgFill = provider.getBackgroundFill();
			IChartFill fgFill = provider.getChartFill();

			StringBuilder fillParam = new StringBuilder();

			if (bgFill != null)
			{
				fillParam.append("bg,").append(render(bgFill));
			}

			if (fgFill != null)
			{
				if (fillParam.length() > 0)
				{
					fillParam.append('|');
				}

				fillParam.append("c,").append(render(fgFill));
			}

			if (fillParam.toString().trim().equals(""))
			{
				fillParam = null;
			}

			addParameter(url, "chf", fillParam);

			IChartAxis[] axes = provider.getAxes();
			addParameter(url, "chxt", renderTypes(axes));
			addParameter(url, "chxl", renderLabels(axes));
			addParameter(url, "chxp", renderPositions(axes));
			addParameter(url, "chxr", renderRanges(axes));
			addParameter(url, "chxs", renderStyles(axes));

			addParameter(url, "chg", render(provider.getGrid()));
			addParameter(url, "chm", render(provider.getShapeMarkers()));
			addParameter(url, "chm", render(provider.getRangeMarkers()));
			addParameter(url, "chm", render(provider.getTextValueMarkers()));
			addParameter(url, "chls", render(provider.getLineStyles()));
			addParameter(url, "chm", render(provider.getFillAreas()));
			addParameter(url, "chl", render(provider.getPieLabels()));

			return url;
		}
		finally
		{
			lock.writeLock().unlock();
		}
	}

	private void addParameter(StringBuilder url, CharSequence param, CharSequence value)
	{
		if (value == null || value.length() == 0)
		{
			return;
		}

		if (url.charAt(url.length() - 1) != '?')
		{
			url.append('&');
		}

		url.append(param).append('=').append(value);
	}

	private CharSequence render(Dimension dim)
	{
		if (dim == null)
		{
			return null;
		}

		return new StringBuilder().append(dim.width).append('x').append(dim.height);
	}

	private CharSequence render(IChartData data)
	{
		if (data == null)
		{
			return null;
		}

		ChartDataEncoding encoding = data.getEncoding();

		StringBuilder back = new StringBuilder();
		back.append(render(encoding)).append(':');

		for (double[] set : data.getData())
		{
			if (set == null || set.length == 0)
			{
				back.append(encoding.convert(-1, data.getMax()));
			}
			else
			{
				for (double value : set)
				{
					back.append(encoding.convert(value, data.getMax())).append(
						encoding.getValueSeparator());
				}

				if (back.substring(back.length() - encoding.getValueSeparator().length(),
					back.length()).equals(encoding.getValueSeparator()))
				{
					back.setLength(back.length() - encoding.getValueSeparator().length());
				}
			}

			back.append(encoding.getSetSeparator());
		}

		if (back.substring(back.length() - encoding.getSetSeparator().length(), back.length())
			.equals(encoding.getSetSeparator()))
		{
			back.setLength(back.length() - encoding.getSetSeparator().length());
		}

		return back;
	}

	private CharSequence render(Enum<?> value)
	{
		if (value == null)
		{
			return null;
		}

		try
		{
			Object back = value.getClass().getMethod("getRendering").invoke(value);

			if (back != null)
			{
				return back.toString();
			}
		}
		catch (IllegalArgumentException e)
		{
		}
		catch (SecurityException e)
		{
		}
		catch (IllegalAccessException e)
		{
		}
		catch (InvocationTargetException e)
		{
		}
		catch (NoSuchMethodException e)
		{
		}

		return null;
	}

	private CharSequence render(int barWidth, int groupSpacing)
	{
		if (barWidth == -1)
		{
			return null;
		}

		StringBuilder back = new StringBuilder("" + barWidth);

		if (groupSpacing >= 0)
		{
			back.append(',').append(groupSpacing);
		}

		return back;
	}

	private CharSequence render(String[] values)
	{
		if (values == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (String value : values)
		{
			CharSequence toRender = render(value);
			if (toRender == null)
			{
				toRender = "";
			}

			back.append(toRender).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence render(String value)
	{
		if (value == null)
		{
			return value;
		}

		StringBuilder back = new StringBuilder();

		for (char c : value.toCharArray())
		{
			if (c == ' ')
			{
				back.append('+');
			}
			else
			{
				back.append(c);
			}
		}

		return back;
	}

	private CharSequence render(Color[] values)
	{
		if (values == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (Color value : values)
		{
			CharSequence toRender = render(value);
			if (toRender == null)
			{
				toRender = "";
			}

			back.append(toRender).append(',');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence render(Color value)
	{
		if (value == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		{
			String toPad = Integer.toHexString(value.getRed());

			if (toPad.length() == 1)
			{
				back.append(0);
			}
			back.append(toPad);
		}

		{
			String toPad = Integer.toHexString(value.getGreen());

			if (toPad.length() == 1)
			{
				back.append(0);
			}
			back.append(toPad);
		}

		{
			String toPad = Integer.toHexString(value.getBlue());

			if (toPad.length() == 1)
			{
				back.append(0);
			}
			back.append(toPad);
		}

		{
			String toPad = Integer.toHexString(value.getAlpha());

			if (toPad.length() == 1)
			{
				back.append(0);
			}
			back.append(toPad);
		}

		return back;
	}

	private CharSequence render(IChartFill fill)
	{
		if (fill == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		if (fill instanceof ISolidFill)
		{
			ISolidFill solidFill = (ISolidFill)fill;

			back.append("s,");
			back.append(render(solidFill.getColor()));
		}
		else if (fill instanceof ILinearGradientFill)
		{
			ILinearGradientFill gradientFill = (ILinearGradientFill)fill;

			back.append("lg,").append(gradientFill.getAngle()).append(',');

			Color[] colors = gradientFill.getColors();
			double[] offsets = gradientFill.getOffsets();
			for (int i = 0; i < colors.length; i++)
			{
				back.append(render(colors[i])).append(',').append(offsets[i]).append(',');
			}

			back.setLength(back.length() - 1);
		}
		else if (fill instanceof ILinearStripesFill)
		{
			ILinearStripesFill stripesFill = (ILinearStripesFill)fill;

			back.append("ls,").append(stripesFill.getAngle()).append(',');

			Color[] colors = stripesFill.getColors();
			double[] widths = stripesFill.getWidths();
			for (int i = 0; i < colors.length; i++)
			{
				back.append(render(colors[i])).append(',').append(widths[i]).append(',');
			}

			back.setLength(back.length() - 1);
		}
		else
		{
			return null;
		}

		return back;
	}

	private CharSequence renderTypes(IChartAxis[] axes)
	{
		if (axes == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (IChartAxis axis : axes)
		{
			back.append(render(axis.getType())).append(',');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence renderLabels(IChartAxis[] axes)
	{
		if (axes == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (int i = 0; i < axes.length; i++)
		{
			if (axes[i] == null || axes[i].getLabels() == null)
			{
				continue;
			}

			back.append(i).append(":|");

			for (String label : axes[i].getLabels())
			{
				if (label == null)
				{
					back.append('|');
					continue;
				}

				back.append(render(label)).append('|');
			}

			if (i == axes.length - 1)
			{
				back.setLength(back.length() - 1);
			}
		}

		return back;
	}

	private CharSequence renderPositions(IChartAxis[] axes)
	{
		if (axes == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (int i = 0; i < axes.length; i++)
		{
			if (axes[i] == null || axes[i].getPositions() == null)
			{
				continue;
			}

			back.append(i).append(',');

			for (double position : axes[i].getPositions())
			{
				back.append(position).append(',');
			}

			back.setLength(back.length() - 1);

			back.append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence renderRanges(IChartAxis[] axes)
	{
		if (axes == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (int i = 0; i < axes.length; i++)
		{
			if (axes[i] == null || axes[i].getRange() == null)
			{
				continue;
			}

			back.append(i).append(',');

			Range range = axes[i].getRange();
			back.append(range.getStart()).append(',').append(range.getEnd()).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence renderStyles(IChartAxis[] axes)
	{
		if (axes == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (int i = 0; i < axes.length; i++)
		{
			if (axes[i] == null || axes[i].getColor() == null || axes[i].getFontSize() < 0 ||
				axes[i].getAlignment() == null)
			{
				continue;
			}

			back.append(i).append(',');
			back.append(render(axes[i].getColor())).append(',');
			back.append(axes[i].getFontSize()).append(',');
			back.append(render(axes[i].getAlignment())).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence render(IChartGrid grid)
	{
		if (grid == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		back.append(grid.getXStepSize()).append(',');
		back.append(grid.getYStepSize());

		if (grid.getSegmentLength() >= 0)
		{
			back.append(',').append(grid.getSegmentLength());
			back.append(',').append(grid.getBlankLength());
		}

		return back;
	}

	private CharSequence render(IShapeMarker[] markers)
	{
		if (markers == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (IShapeMarker marker : markers)
		{
			back.append(render(marker.getType())).append(',');
			back.append(render(marker.getColor())).append(',');
			back.append(marker.getIndex()).append(',');
			back.append(marker.getPoint()).append(',');
			back.append(marker.getSize()).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence render(IRangeMarker[] markers)
	{
		if (markers == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (IRangeMarker marker : markers)
		{
			back.append(render(marker.getType())).append(',');
			back.append(render(marker.getColor())).append(',');
			back.append(0).append(',');
			back.append(marker.getStart()).append(',');
			back.append(marker.getEnd()).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence render(ITextValueMarker[] markers)
	{
		if (markers == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (ITextValueMarker marker : markers)
		{
			back.append(render(marker.getType()));
			back.append(render(marker.getText())).append(',');
			back.append(render(marker.getColor())).append(',');
			back.append(marker.getIndex()).append(',');
			back.append(marker.getPoint()).append(',');
			back.append(marker.getSize()).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence render(IFillArea[] areas)
	{
		if (areas == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (IFillArea area : areas)
		{
			back.append(render(area.getType())).append(',');
			back.append(render(area.getColor())).append(',');
			back.append(area.getStartIndex()).append(',');
			back.append(area.getEndIndex()).append(',');
			back.append(0).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	private CharSequence render(ILineStyle[] styles)
	{
		if (styles == null)
		{
			return null;
		}

		StringBuilder back = new StringBuilder();

		for (ILineStyle style : styles)
		{
			if (style == null)
			{
				back.append('|');
				continue;
			}

			back.append(style.getThickness()).append(',');
			back.append(style.getSegmentLength()).append(',');
			back.append(style.getBlankLength()).append('|');
		}

		if (back.length() > 0)
		{
			back.setLength(back.length() - 1);
		}

		return back;
	}

	@Override
	protected void onComponentTag(ComponentTag tag)
	{
		checkComponentTag(tag, "img");
		super.onComponentTag(tag);

		tag.put("src", constructURL());
	}
}
